<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>语音测试 - 录音文件识别</title>
    <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
    <style>
        body {
            padding: 20px;
            max-width: 800px;
            margin: 0 auto;
        }

        .container {
            margin-top: 20px;
        }

        .btn-group {
            margin-bottom: 15px;
        }

        .status {
            margin-top: 10px;
            padding: 10px;
            border-radius: 5px;
        }

        .status-success {
            background-color: #d4edda;
            color: #155724;
        }

        .status-error {
            background-color: #f8d7da;
            color: #721c24;
        }

        .status-info {
            background-color: #cce5ff;
            color: #004085;
        }

        textarea {
            resize: vertical;
            min-height: 100px;
        }

        .audio-player {
            margin-top: 15px;
        }

        .option-group {
            margin-bottom: 15px;
        }

        .live-indicator {
            display: inline-block;
            width: 10px;
            height: 10px;
            border-radius: 50%;
            background-color: #28a745;
            margin-right: 5px;
            animation: blink 1s infinite;
        }

        @keyframes blink {
            0%, 50% {
                opacity: 1;
            }
            51%, 100% {
                opacity: 0.3;
            }
        }
    </style>
</head>
<body>
<div class="container">
    <h1 class="mb-4">实时ASR功能</h1>
    <!-- ASR语音识别部分 -->
    <div class="card">
        <div class="card-header bg-success text-white">
            <h2 class="h5 mb-0">语音识别 (ASR)</h2>
        </div>
        <div class="card-body">
            <div class="option-group">
                <div class="form-check form-check-inline">
                    <input class="form-check-input" type="radio" name="asrOption" id="optionLive" checked>
                    <label class="form-check-label" for="optionLive">录音文件识别</label>
                </div>
            </div>

            <!-- 识别结果 -->
            <div class="mb-3">
                <label for="asrText" class="form-label">识别结果：</label>
                <textarea class="form-control" id="asrText" placeholder="语音识别结果将显示在这里..."
                          readonly></textarea>
            </div>

            <!-- 录音控制按钮组 -->
            <div class="btn-group" role="group" id="liveButtons">
                <button id="startRecordBtn" class="btn btn-success">开始录音</button>
                <button id="stopRecordBtn" class="btn btn-danger" disabled>停止录音</button>
                <button id="recognizeBtn" class="btn btn-primary" disabled>开始识别</button>
            </div>

            <div id="asrStatus" class="status mt-3"></div>
            <div id="fileInfo" class="mt-2 text-sm text-gray-600"></div>
        </div>
    </div>
</div>

<script>
    // 全局变量
    let mediaStream = null;
    let audioContext = null;
    let microphone = null;
    let scriptProcessor = null;
    let pcmData = [];
    let savedFilePath = '';

    // 获取DOM元素引用
    const asrStatus = document.getElementById('asrStatus');
    const startRecordBtn = document.getElementById('startRecordBtn');
    const stopRecordBtn = document.getElementById('stopRecordBtn');
    const recognizeBtn = document.getElementById('recognizeBtn');
    const fileInfo = document.getElementById('fileInfo');

    // 开始录音
    startRecordBtn.addEventListener('click', async () => {
        try {
            // 检查浏览器支持
            if (!('getUserMedia' in navigator.mediaDevices)) {
                showStatus(asrStatus, '错误', '浏览器不支持麦克风采集功能', 'error');
                return;
            }

            // 获取用户媒体设备（麦克风）
            mediaStream = await navigator.mediaDevices.getUserMedia({
                audio: {
                    echoCancellation: true,
                    noiseSuppression: true,
                    sampleRate: 16000
                }
            });

            // 创建音频上下文
            audioContext = new (window.AudioContext || window.webkitAudioContext)({
                sampleRate: 16000  // 设置采样率为16kHz，匹配讯飞ASR要求
            });

            // 创建麦克风输入节点
            microphone = audioContext.createMediaStreamSource(mediaStream);

            // 创建音频处理器节点
            // 注意：scriptProcessorNode已被弃用，但兼容性最好
            // 现代浏览器推荐使用AudioWorklet
            scriptProcessor = audioContext.createScriptProcessor(4096, 1, 1);

            // 清空之前的PCM数据
            pcmData = [];

            // 音频处理回调函数
            scriptProcessor.onaudioprocess = function (event) {
                // 获取输入音频数据（单声道）
                const inputData = event.inputBuffer.getChannelData(0);

                // 将Float32数据转换为16位PCM整数
                const int16Data = new Int16Array(inputData.length);
                for (let i = 0; i < inputData.length; i++) {
                    // 音频数据归一化并转换为16位整数
                    const s = Math.max(-1, Math.min(1, inputData[i]));
                    int16Data[i] = s < 0 ? s * 0x8000 : s * 0x7FFF;
                }

                // 存储PCM数据
                pcmData.push(...int16Data);
            };

            // 连接音频节点
            microphone.connect(scriptProcessor);
            scriptProcessor.connect(audioContext.destination);

            // 更新UI状态
            startRecordBtn.disabled = true;
            stopRecordBtn.disabled = false;
            recognizeBtn.disabled = true;
            showStatus(asrStatus, '录音中', '<span class="live-indicator"></span>正在录音，请说话...', 'info');
            fileInfo.textContent = '';

        } catch (error) {
            console.error('启动录音失败:', error);
            showStatus(asrStatus, '错误', '无法启动录音：' + error.message, 'error');
            resetRecordButtons();
        }
    });

    // 停止录音
    stopRecordBtn.addEventListener('click', () => {
        try {
            if (scriptProcessor) {
                scriptProcessor.onaudioprocess = null;
                scriptProcessor.disconnect();
                scriptProcessor = null;
            }

            if (microphone) {
                microphone.disconnect();
                microphone = null;
            }

            if (mediaStream) {
                mediaStream.getTracks().forEach(track => track.stop());
                mediaStream = null;
            }

            // 保存录音文件到后端
            saveAudioToServer();

        } catch (error) {
            console.error('停止录音失败:', error);
            showStatus(asrStatus, '错误', '停止录音时出错', 'error');
        }
    });

    // 开始识别
    recognizeBtn.addEventListener('click', async () => {
        if (!savedFilePath) {
            showStatus(asrStatus, '错误', '请先录音并保存文件', 'error');
            return;
        }

        try {
            showStatus(asrStatus, '识别中', '正在识别语音...', 'info');

            // 调用文件识别接口
            const response = await axios.post('http://localhost:8080/user/ai/asr/file', null, {
                params: {filePath: savedFilePath},
                timeout: 30000
            });

            if (response.data.code === 1 && response.data.data) {
                console.log('识别成功，结果:', response.data.data);
                document.getElementById('asrText').value = response.data.data;
                showStatus(asrStatus, '识别成功', '语音识别完成', 'success');
            } else {
                console.warn('ASR识别结果为空或失败:', response.data);
                showStatus(asrStatus, '错误', '语音识别失败: ' + (response.data.message || '未知错误'), 'error');
            }
        } catch (error) {
            console.error('ASR请求错误:', error);
            showStatus(asrStatus, '错误', '语音识别请求失败: ' + (error.message || '未知错误'), 'error');
        }
    });

    // 保存音频文件到服务器
    async function saveAudioToServer() {
        try {
            // 生成唯一文件名
            const timestamp = Date.now();
            const fileName = `recording_${timestamp}.pcm`;

            // 将Int16Array转换为Blob
            const blob = new Blob([new Int16Array(pcmData)], {type: 'application/octet-stream'});

            const formData = new FormData();
            formData.append('audioFile', blob, fileName);

            // 调用保存文件接口
            const response = await axios.post('http://localhost:8080/user/ai/save-audio', formData, {
                timeout: 10000,
                headers: {
                    'Content-Type': 'multipart/form-data'
                }
            });

            if (response.data.code === 1 && response.data.data) {
                savedFilePath = response.data.data;
                console.log('文件保存成功，路径:', savedFilePath);
                showStatus(asrStatus, '录音完成', '录音已保存，请点击"开始识别"进行处理', 'success');
                fileInfo.textContent = `保存的文件: ${fileName} (${(blob.size / 1024).toFixed(2)} KB)`;
                recognizeBtn.disabled = false;
            } else {
                console.error('文件保存失败:', response.data);
                showStatus(asrStatus, '错误', '文件保存失败: ' + (response.data.message || '未知错误'), 'error');
            }
        } catch (error) {
            console.error('保存音频文件失败:', error);
            showStatus(asrStatus, '错误', '保存录音文件失败: ' + error.message, 'error');
        } finally {
            resetRecordButtons();
        }
    }

    // 重置录音按钮状态
    function resetRecordButtons() {
        startRecordBtn.disabled = false;
        stopRecordBtn.disabled = true;
    }

    // 工具函数showStatus
    function showStatus(element, status, message, type) {
        element.innerHTML = message; // 使用innerHTML而不是textContent，以支持HTML标签
        element.className = `status status-${type}`;
    }
</script>
</body>
</html>